﻿/**
 * 8051虚拟机具体实现
 *
 * 创建时间：2022年5月21日
 * 作者：李述铜
 * 联系邮箱: 527676163@qq.com
 */
#include <string.h>
#include <stdio.h>
#include "simu8051.h"

// 8051 simulator
static simu8051_t simu;

/**
 * 读取位寻址区，包含0
 */
static uint8_t read_bit (uint8_t addr) {
    uint8_t bit_idx = addr % 8;

    if (addr < MEM_BIT_SFR_START) {
        uint8_t byte_idx = addr / 8 + MEM_BIT_IRAM_START;
        return simu.mem.iram[byte_idx] & ( 1 << bit_idx) ? 1 : 0;
    } else {
        uint8_t byte_idx = (addr - MEM_BIT_SFR_START) / 8 * 8;
        return simu.mem.sfr[byte_idx] & (1 << bit_idx) ? 1 : 0;
    }
}

static void write_bit (uint8_t addr, int bit) {
    uint8_t bit_idx = addr % 8;

    bit &= 0x1;
    if (addr < MEM_BIT_SFR_START) {
        uint8_t byte_idx = addr / 8 + MEM_BIT_IRAM_START;
        simu.mem.iram[byte_idx] &= ~(1 << bit_idx);
        simu.mem.iram[byte_idx] |= bit ? (1 << bit_idx) : 0;
    } else {
        uint8_t byte_idx = (addr - MEM_BIT_SFR_START) / 8 * 8;
        simu.mem.sfr[byte_idx] &= ~(1 << bit_idx);
        simu.mem.sfr[byte_idx] |= bit ? (1 << bit_idx) : 0;
    }
}

static uint8_t read_sfr(uint8_t addr) {
    switch (addr) {
        default: {
            addr -= MEM_BIT_SFR_START;
            return (addr < MEM_SFR_SIZE) ? simu.mem.sfr[addr] : 0;
        }
    }
}

static void write_sfr(uint8_t addr, uint8_t data) {
    switch (addr) {
        default: {
            addr -= MEM_BIT_SFR_START;
            if (addr < MEM_SFR_SIZE) {
                simu.mem.sfr[addr] = data;
            }
            break;
        }
    }
}

/**
 * read internal ram
 */
static uint8_t read_iram (uint8_t addr) {
    uint8_t data;

    if (addr >= MEM_IRAM_SIZE) {
        data = read_sfr(addr);
    } else {
        data = simu.mem.iram[addr];
    }

    return data;
}

/**
 * write internal ram
 */
static void write_iram (uint8_t addr, uint8_t data) {
    if (addr >= MEM_IRAM_SIZE) {
        write_sfr(addr, data);
    } else {
        simu.mem.iram[addr] = data;
    }
}

/**
 * read external ram
 */
static uint8_t read_xram (uint16_t addr) {
    return simu.mem.xram[addr];
}

/**
 * write external ram
 */
static void write_xram (uint16_t addr, uint8_t data) {
    simu.mem.xram[addr] = data;
}

/**
 * read code memory
 */
static uint8_t read_code (uint16_t addr) {
    return simu.mem.code[addr];
}

/**
 * write code ram
 */
static void write_code(uint16_t addr, uint8_t data) {
    simu.mem.code[addr] = data;
}

uint8_t simu8051_read (mem_type_t type, uint16_t addr) {
    switch (type) {
    case MEM_TYPE_IRAM:
        return read_iram((uint8_t)addr);
    case MEM_TYPE_XRAM:
        return read_xram(addr);
    case MEM_TYPE_CODE:
        return read_code(addr);
    case MEM_TYPE_SFR:
        return read_sfr((uint8_t)addr);
    case MEM_TYPE_BIT:
        return read_bit((uint8_t)addr);
    default:
        return 0;
    }
}

void simu8051_write (mem_type_t type, uint16_t addr, uint8_t data) {
    switch (type) {
    case MEM_TYPE_IRAM:
        write_iram((uint8_t)addr, data);
        break;
    case MEM_TYPE_XRAM:
        write_xram(addr, data);
        break;
    case MEM_TYPE_CODE:
        write_code(addr, data);
        break;
    case MEM_TYPE_SFR:
        write_sfr((uint8_t)addr, data);
        break;
    case MEM_TYPE_BIT:
        write_bit((uint8_t) addr, data);
        break;
    }
}

static uint8_t get_op (instr_t * instr, int op_num) {
    const instr_info_t * info = instr->info;

    if (op_num == 0) {
        switch (info->op0_mode) {
        case OTYPE_IMM8:
        case OTYPE_DIRECT:
           return instr->op0;
        default:
            return 0;
        }
    } else {
        return (info->bytes > 2) ? instr->op1 : instr->op0;
    }
}

/**
 * read src operand
 */
static uint16_t read_op(instr_t * instr, int op_num, mem_type_t mem_type) {
    const instr_info_t * info = instr->info;

    OTYPE_t op_mode = op_num ? info->op1_mode : info->op0_mode;
    uint8_t op = get_op(instr, op_num);
    switch (op_mode) {
        case OTYPE_Rn: {
            int reg = instr->opcode & 0x7;
            return simu.mem.iram[reg];
        }
        case OTYPE_IMM8: {
            return op;
        }
        case OTYPE_ACC: {
            return read_sfr(SFR_ACC);
        }
        case OTYPE_DIRECT: {
            return read_iram(op);
        }
        default:
            return 0;
    }
}

static void write_op(instr_t * instr, uint16_t data, uint8_t op_num, mem_type_t mem_type) {
    const instr_info_t * info = instr->info;

    OTYPE_t op_mode = op_num ? info->op1_mode : info->op0_mode;
    uint8_t op = get_op(instr, op_num);
    switch (op_mode) {
       case OTYPE_Rn: {
            int reg = instr->opcode & 0x7;
            simu.mem.iram[reg] = (uint8_t)data;
            break;
        }
        case OTYPE_IMM8:
            break;
        case OTYPE_ACC: {
            write_sfr(SFR_ACC, (uint8_t)data);
            break;
        }
        case OTYPE_DIRECT: {
            write_iram(op, (uint8_t)data);
            break;
        }
    }
}

/**
 * NOP
 * nop                  0 0 0 0 0 0 0 0
 */
static void do_nop(instr_t * instr) {
    simu.pc += instr->info->bytes;
    simu.cycles += instr->info->cycles;
}
/**
 * MOV <destbyte>,<src-byte>
 * -MOV A,Rn             1 1 1 0 1 r r r (acc, rn)
 * -MOV A,direct         1 1 1 0 0 1 0 1     direct address (acc, dir)
 * -MOV A,@Ri            1 1 1 0 0 1 1 i
 * -MOV A,#data          0 1 1 1 0 1 0 0     immediate data
 * -MOV Rn, A            1 1 1 1 1 r r r (rn, acc)
 * -MOV Rn,direct        1 0 1 0 1 r r r     direct address
 * -MOV Rn,#data         0 1 1 1 1 r r r     immediate data
 * -MOV direct,A         1 1 1 1 0 1 0 1     direct address (dir, acc)
 * -MOV direct,Rn        1 0 0 0 1 r r r     direct address
 * -MOV direct,direct    1 0 0 0 0 1 0 1     dir. addr. (scr)    dir. addr. (dest)
 * -MOV direct,@Ri       1 0 0 0 0 1 1 i     direct addr.
 * -MOV direct,#data     0 1 1 1 0 1 0 1     direct address      immediate data
 * -MOV @Ri,A            1 1 1 1 0 1 1 i
 * -MOV @Ri,direct       1 0 1 0 0 1 1 i     direct addr
 * -MOV @Ri,#data        0 1 1 1 0 1 1 i     immediate data
 * -MOV DPTR,#data16     1 0 0 1 0 0 0 0     immed. data15-8     immed. data7-0
 * -MOV C,bit            1 0 1 0 0 0 1 0     bit address
 * -MOV bit,C            1 0 0 1 0 0 1 0     bit address
 */
static void do_mov(instr_t * instr) {
    const instr_info_t * info = instr->info;

    uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
    write_op(instr, src, 0, MEM_TYPE_IRAM);

    simu.pc += instr->info->bytes;
    simu.cycles += instr->info->cycles;
}

/**
 * SJMP rel             1 0 0 0 0 0 0 0     rel. address
 */
static void do_sjmp(instr_t * instr) {
    simu.pc += instr->info->bytes;
    simu.pc += (int8_t)instr->op0;
    simu.cycles += instr->info->cycles;
}

// instruction lookup table
static const instr_info_t instr_table[256] = {
    [0x00] = {1, 1, OTYPE_NONE, OTYPE_NONE, do_nop,   "nop"},

    [0x74] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_mov,  "MOV A, #imm8"},
    [0x78] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R0,#imm8"},
    [0x79] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R1,#imm8"},
    [0x7A] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R2,#imm8"},
    [0x7B] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R3,#imm8"},
    [0x7C] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R4,#imm8"},
    [0x7D] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R5,#imm8"},
    [0x7E] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R6,#imm8"},
    [0x7F] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov,   "MOV R7,#imm8"},

    [0x80] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_sjmp,  "sjmp rel"},
    [0x88] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R0"},
    [0x89] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R1"},
    [0x8A] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R2"},
    [0x8B] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R3"},
    [0x8C] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R4"},
    [0x8D] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R5"},
    [0x8E] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R6"},
    [0x8F] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov,   "MOV direct, R7"},

    [0xE8] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R0"},
    [0xE9] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R1"},
    [0xEA] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R2"},
    [0xEB] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R3"},
    [0xEC] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R4"},
    [0xED] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R5"},
    [0xEE] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R6"},
    [0xEF] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov,   "MOV A,R7"},

    [0xF5] = {2, 1, OTYPE_DIRECT, OTYPE_ACC, do_mov, "MOV direct, A"},

    [0xF8] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R0,A"},
    [0xF9] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R1,A"},
    [0xFA] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R2,A"},
    [0xFB] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R3,A"},
    [0xFC] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R4,A"},
    [0xFD] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R5,A"},
    [0xFE] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R6,A"},
    [0xFF] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov,   "MOV R7,A"},
};

void simu8051_reset(void) {
    simu.pc = 0x0;
    simu.cycles = 0;
    write_sfr(SFR_SP, 0x7);
}

/**
 * initialize 8051 simulator
 */
void simu8051_init(void) {
    simu.mem.code = (uint8_t *)0;
    memset(simu.mem.xram, 0, MEM_XRAM_SIZE);
    memset(simu.mem.iram, 0, MEM_IRAM_SIZE);
    memset(simu.mem.sfr, 0, MEM_SFR_SIZE);

    simu8051_reset();
}

uint16_t simu8051_pc(void) {
    return simu.pc;
}

uint32_t simu8051_cycle(void) {
    return simu.cycles;
}

void simu8051_fetch_instr(instr_t * instr) {
    const instr_info_t * info;

    instr->opcode = simu.mem.code[simu.pc];
    info = instr_table + instr->opcode;

    instr->info = info;
    instr->op0 = (info->bytes > 1) ? simu.mem.code[simu.pc + 1] : 0;
    instr->op1 = (info->bytes > 2) ? simu.mem.code[simu.pc + 2] : 0;
}

void simu8051_exec (instr_t * instr) {
    instr->info->exec(instr);

    //switch (instr->opcode) {
    //    case 0x00: {
    //        printf("%d: %s\n", simu.pc, "nop");
    //        simu.pc += 1;
    //        simu.cycles += 1;
    //        break;
    //    }
    //    case 0x80: {
    //        printf("%d: %s\n", simu.pc, "sjmp");
    //        simu.pc += 2 + (int8_t)instr->op0;
    //        simu.cycles += 2;
    //        break;
    //    }
    //}
}

void simu8051_load(uint8_t * code) {
    simu.mem.code = code;
}
